﻿using System;
using System.Reflection;

namespace ReflectionHelper
{
    /// <summary>
    /// An attribute which may be applied to a field,
    /// most frequently used on individual <see cref="Enum"/>
    /// values, to expose a "friendly" name and description.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Although the compiler allows this attribute to
    /// decorate any <see cref="AttributeTargets.Field"/>,
    /// it should be used only on the fields corresponding
    /// to enumerated values of an <see cref="Enum"/> type.
    /// </para>
    /// </remarks>
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false), CLSCompliant(true)]
    public sealed class EnumDisplayNameAttribute : Attribute
    {
        #region [ Private Backing Fields and Public Constructor Overloads ]

        /// <summary>
        /// Backing field for the <see cref="Name"/> property.
        /// </summary>
        private readonly string name;

        /// <summary>
        /// Backing field for the <see cref="ShortName"/> property.
        /// </summary>
        private readonly string shortName;

        /// <summary>
        /// Backing field for the <see cref="Description"/> property.
        /// </summary>
        private readonly string description;

        /// <summary>
        /// Initializes a new instance of the <see cref="EnumDisplayNameAttribute"/> class.
        /// </summary>
        public EnumDisplayNameAttribute()
        {
            this.name = string.Empty;
            this.description = string.Empty;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EnumDisplayNameAttribute"/> class
        /// with an initial value for the <see cref="Name"/> property.
        /// </summary>
        /// <param name="name">
        /// A value to use for the
        /// <see cref="Name"/> property.
        /// </param>
        public EnumDisplayNameAttribute(string name)
        {
            this.name = name.Trim();
            this.description = string.Empty;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="EnumDisplayNameAttribute"/> class with
        /// initial values for the <see cref="Name"/> and <see cref="ShortName"/> properties.
        /// </summary>
        /// <param name="name">
        /// A value to use for the
        /// <see cref="Name"/> property.
        /// </param>
        /// A value to use for the
        /// <see cref="ShortName"/> property.
        /// </param>
        public EnumDisplayNameAttribute(string name, string shortName)
        {
            this.name = name.Trim();
            this.shortName = shortName.Trim();
        }

        /*
        /// <summary>
        /// Initializes a new instance of the <see cref="EnumDisplayNameAttribute"/> class with
        /// initial values for the <see cref="Name"/> and <see cref="Description"/> properties.
        /// </summary>
        /// <param name="name">
        /// A value to use for the
        /// <see cref="Name"/> property.
        /// </param>
        /// <param name="description">
        /// A value to use for the
        /// <see cref="Description"/> property.
        /// </param>
        public EnumDisplayNameAttribute(string name, string description)
        {
            this.name = name.Trim();
            this.description = description.Trim();
        }
        */

        /// <summary>
        /// Initializes a new instance of the <see cref="EnumDisplayNameAttribute"/> class with
        /// initial values for the <see cref="Name"/>, <see cref="ShortName"/> and <see cref="Description"/> properties.
        /// </summary>
        /// <param name="name">
        /// A value to use for the
        /// <see cref="Name"/> property.
        /// </param>
        /// A value to use for the
        /// <see cref="ShortName"/> property.
        /// </param>
        /// <param name="description">
        /// A value to use for the
        /// <see cref="Description"/> property.
        /// </param>
        public EnumDisplayNameAttribute(string name, string shortName, string description)
        {
            this.name = name.Trim();
            this.description = description.Trim();
            this.shortName = shortName.Trim();
        }

        #endregion

        /// <summary>
        /// Gets an optional friendly name for this field in the
        /// enumerated values of an <see cref="Enum" />
        /// type, to be displayed in the user interface.
        /// </summary>
        public string Name
        {
            get { return this.name; }
        }

        /// <summary>
        /// Gets an optional friendly short name for this field in the
        /// enumerated values of an <see cref="Enum" />
        /// type, to be displayed in the user interface.
        /// </summary>
        public string ShortName
        {
            get { return this.shortName; }
        }

        /// <summary>
        /// Gets an optional description for this field in the
        /// enumerated values of an <see cref="Enum" />
        /// type, to be displayed in the user interface.
        /// </summary>
        public string Description
        {
            get { return this.description; }
        }

        /// <summary>
        /// Fetches an instance of <see cref="EnumDisplayNameAttribute"/>
        /// defined on a field in the enumerated values
        /// of an <see cref="Enum"/> type.
        /// </summary>
        /// <param name="enumValue">
        /// A value of an <see cref="Enum"/> type.
        /// The value must be a single, defined field in the enumeration.
        /// </param>
        /// <returns>
        /// The <see cref="EnumDisplayNameAttribute"/> defined on
        /// a single value in an enumeration, or a null reference
        /// if there is none defined.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="enumValue"/> parameter is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="enumValue"/> parameter
        /// is not of a type derived from <see cref="Enum"/>,
        /// or its value does is not singularly defined in the type.
        /// </exception>
        public static EnumDisplayNameAttribute GetEnumInfo(object enumValue)
        {
            if (enumValue == null)
            {
                throw new ArgumentNullException("enumValue", "Cannot retrieve the text from the NameAttribute of a null value.");
            }

            Type enumType = enumValue.GetType();

            if (!enumType.IsEnum)
            {
                throw new ArgumentException("The value must be of a System.Enum type.", "enumValue");
            }

            if (!Enum.IsDefined(enumType, enumValue))
            {
                throw new ArgumentException(
                    string.Format(
                        "The value must correspond to a single enumerated field, and \"{0}\" fails to meet this condition for the {1} type.",
                        enumValue,
                        enumType.FullName));
            }

            // Since we know the value to be defined for its type,
            // the "GetField" method should never return null.
            FieldInfo fieldInfo = enumType.GetField(Enum.GetName(enumType, enumValue));

            // Check whether this enumerated value
            // has an EnumDisplayNameAttribute defined for it.
            if (!fieldInfo.IsDefined(typeof(EnumDisplayNameAttribute), false))
            {
                return null;
            }

            // Since the AttributeUsage attribute specifies
            // AllowMultiple = false and we know that
            // there is an EnumDisplayNameAttribute defined,
            // we can be certain this array contains
            // a single EnumDisplayNameAttribute instance.
            object[] ary = fieldInfo.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false);

            // Return a safe cast of the array's element.
            return ary[0] as EnumDisplayNameAttribute;
        }

        /// <summary>
        /// Tries the get the value of an enumeration
        /// corresponding to <paramref name="name"/>.
        /// </summary>
        /// <typeparam name="T">
        /// Type of the enumeration.
        /// </typeparam>
        /// <param name="name">
        /// String representing either the enumeration name
        /// or the name of the <see cref="EnumDisplayNameAttribute"/>.
        /// </param>
        /// <param name="value">
        /// An output parameter representing the enumeration value.
        /// </param>
        /// <returns>
        /// <c>true</c> if the value mapped; otherwise, <c>false</c>.
        /// </returns>
        public static bool TryGetValue<T>(string name, out T value) where T : struct
        {
            Type enumType = typeof(T);
            value = default(T);

            if (!System.Enum.IsDefined(typeof(T), name))
            {
                return false;
            }

            value = (T)System.Enum.Parse(enumType, name);
            if (value.Equals(default(T)))
            {
                foreach (FieldInfo info in typeof(T).GetFields())
                {
                    object[] arr = info.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false);
                    if (arr.Length == 0)
                    {
                        continue;
                    }

                    EnumDisplayNameAttribute infoAttribute = arr[0] as EnumDisplayNameAttribute;
                    if (infoAttribute == null)
                    {
                        continue;
                    }

                    if (name.Equals(infoAttribute.Name, StringComparison.OrdinalIgnoreCase))
                    {
                        value = (T)System.Enum.Parse(enumType, name);
                        break;
                    }
                }
            }

            return (!value.Equals(default(T))) && Enum.IsDefined(typeof(T), value);
        }

        /// <summary>
        /// Returns the value of the <see cref="Name"/>
        /// property of an <see cref="EnumDisplayNameAttribute"/>
        /// instance if specified, or a standard string
        /// representation of an enumerated value.
        /// </summary>
        /// <param name="enumValue">
        /// A value for an <see cref="Enum"/> type.
        /// </param>
        /// <returns>
        /// A string representing the <paramref name="enumValue"/> parameter.
        /// </returns>
        public static string GetName(object enumValue)
        {
            // Verification that the parameter is an enum
            // and that the value corresponds to a single field
            // occurs in the GetEnumInfo method.
            EnumDisplayNameAttribute enumInfo = GetEnumInfo(enumValue);

            // If an attribute is defined with a value for name...
            if ((enumInfo != null) && (!string.IsNullOrEmpty(enumInfo.Name)))
            {
                // ...then return the name.
                return enumInfo.Name;
            }

            // If no EnumDisplayNameAttribute is specified,
            // or the attribute has no value for the name,
            // return the standard name for the value.
            return Enum.GetName(enumValue.GetType(), enumValue);
        }


        /// <summary>
        /// Returns the value of the <see cref="ShortName"/>
        /// property of an <see cref="EnumDisplayNameAttribute"/>
        /// instance if specified, or a standard string
        /// representation of an enumerated value.
        /// </summary>
        /// <param shortName="enumValue">
        /// A value for an <see cref="Enum"/> type.
        /// </param>
        /// <returns>
        /// A string representing the <paramref shortName="enumValue"/> parameter.
        /// </returns>
        public static string GetShortName(object enumValue)
        {
            // Verification that the parameter is an enum
            // and that the value corresponds to a single field
            // occurs in the GetEnumInfo method.
            EnumDisplayNameAttribute enumInfo = GetEnumInfo(enumValue);

            // If an attribute is defined with a value for name...
            if ((enumInfo != null) && (!string.IsNullOrEmpty(enumInfo.ShortName)))
            {
                // ...then return the name.
                return enumInfo.ShortName;
            }

            // If no EnumDisplayNameAttribute is specified,
            // or the attribute has no value for the name,
            // return the standard name for the value.
            return Enum.GetName(enumValue.GetType(), enumValue);
        }
    }

}